/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.encoding.base64;

import org.jetbrains.annotations.NotNull;

import java.io.*;

public class Base64Encoder
{
	@NotNull
	public String encode(final @NotNull byte[] data)
	{
		final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		try
		{
			encode(new ByteArrayInputStream(data), outputStream);
		}
		catch (IOException e)
		{
			throw new IllegalStateException("Should never happen", e);
		}
		try
		{
			return outputStream.toString("8859_1");
		}
		catch (UnsupportedEncodingException e)
		{
			throw new IllegalStateException("Java is broken", e);
		}
	}

	private static final char PEM[] =
	{
			'A',
			'B',
			'C',
			'D',
			'E',
			'F',
			'G',
			'H',
			'I',
			'J',
			'K',
			'L',
			'M',
			'N',
			'O',
			'P',
			'Q',
			'R',
			'S',
			'T',
			'U',
			'V',
			'W',
			'X',
			'Y',
			'Z',
			'a',
			'b',
			'c',
			'd',
			'e',
			'f',
			'g',
			'h',
			'i',
			'j',
			'k',
			'l',
			'm',
			'n',
			'o',
			'p',
			'q',
			'r',
			's',
			't',
			'u',
			'v',
			'w',
			'x',
			'y',
			'z',
			'0',
			'1',
			'2',
			'3',
			'4',
			'5',
			'6',
			'7',
			'8',
			'9',
			'+',
			'/'
	};
	private static final int BytesPerAtom = 3;
	private static final int BytesPerLine = 57;

	private void encode(final @NotNull InputStream inputstream, final @NotNull OutputStream outputstream) throws IOException
	{
		final byte[] data = new byte[BytesPerLine];
		do
		{
			int length = readFully(inputstream, data);
			if (length == 0)
			{
				break;
			}
			for (int i = 0; i < length; i += BytesPerAtom)
			{
				if (i + BytesPerAtom <= length)
				{
					encodeAtom(outputstream, data, i, BytesPerAtom);
				}
				else
				{
					encodeAtom(outputstream, data, i, length - i);
				}
			}

			if (length < BytesPerLine)
			{
				break;
			}
			encodeLineSuffix(outputstream);
		}
		while (true);
	}

	private int readFully(final @NotNull InputStream inputstream, final @NotNull byte[] data) throws IOException
	{
		for (int index = 0; index < data.length; index++)
		{
			int readByte = inputstream.read();
			if (readByte == -1)
			{
				return index;
			}
			data[index] = (byte) readByte;
		}
		return data.length;
	}

	private void encodeLineSuffix(final OutputStream outputStream) throws IOException
	{
		new PrintStream(outputStream).print('\n');
	}

	private void encodeAtom(final @NotNull OutputStream outputstream, final @NotNull byte[] data, final int i, final int length) throws IOException
	{
		if (length == 1)
		{
			byte byte0 = data[i];
			int k = 0;
			outputstream.write(PEM[byte0 >>> 2 & 63]);
			outputstream.write(PEM[(byte0 << 4 & 48) + (k >>> 4 & 15)]);
			outputstream.write(61);
			outputstream.write(61);
		}
		else if (length == 2)
		{
			byte byte1 = data[i];
			byte byte3 = data[i + 1];
			int l = 0;
			outputstream.write(PEM[byte1 >>> 2 & 63]);
			outputstream.write(PEM[(byte1 << 4 & 48) + (byte3 >>> 4 & 15)]);
			outputstream.write(PEM[(byte3 << 2 & 60) + (l >>> 6 & 3)]);
			outputstream.write(61);
		}
		else
		{
			byte byte2 = data[i];
			byte byte4 = data[i + 1];
			byte byte5 = data[i + 2];
			outputstream.write(PEM[byte2 >>> 2 & 63]);
			outputstream.write(PEM[(byte2 << 4 & 48) + (byte4 >>> 4 & 15)]);
			outputstream.write(PEM[(byte4 << 2 & 60) + (byte5 >>> 6 & 3)]);
			outputstream.write(PEM[byte5 & 63]);
		}
	}

}
